// i.d(t, {
//   aw: () => h,
//   b3: () => DataSync,
//   nM: () => ObjectSync,
//   iY: () => CollectionSync
// })

import Logger from "../utils/Logger"

import { SaveErrorMessage } from "../message/data.message"
import funcUtil from "../utils/func.util"
import GlobalState from "../object/GlobalState"
import { ChangeAction, CollectionMonitor } from "../collection/CollectionMonitor"
import { MarketMonitor } from "../module/CameraStartModule"
import MapMonitor from "../collection/MapMonitor"
import Monitor from "../collection/Monitor"
const l = new Logger("datasync")
var c = {
    "0": "NONE",
    "1": "LEGACY_SYNC",
    "2": "IGNORE_LEGACY_SAVE",
    NONE: 0,
    LEGACY_SYNC: 1,
    IGNORE_LEGACY_SAVE: 2
  },
  d = {
    "0": "IDLE",
    "1": "LOADING",
    "2": "CLEAN",
    "3": "DIRTY",
    IDLE: 0,
    LOADING: 1,
    CLEAN: 2,
    DIRTY: 3
  }

export class DataSync {
  state: number
  diff: any
  deletedKeys: any[]
  config: any
  collectionData: any
  static configuredSyncs: any
  static sharedStore: any
  static sharedState: any
  static msgBus: any
  static afterPublishCallback: any
  data: any
  publishedData: any
  _monitor: MarketMonitor | MapMonitor | Monitor
  constructor(e, t) {
    this.state = d.IDLE
    this.diff = {}
    this.deletedKeys = []
    this.config = e
    this.collectionData = t
    this.config.extras || (this.config.extras = 0)
    this.config.extras & c.LEGACY_SYNC || DataSync.configuredSyncs.push(this)
  }
  static setGlobalStore(e) {
    if (this.sharedStore) throw Error("Attempting to overwrite sync's internal store")
    this.sharedStore = e
    this.sharedStore.read().then(e => {
      this.sharedState.lastPublished = e.lastPublished
      this.sharedState.commit()
    })
  }
  static setMessageBus(e) {
    this.msgBus = e
  }
  static setAfterPublish(e) {
    DataSync.afterPublishCallback = e
  }
  static async publishAll() {
    const e = this.configuredSyncs.map(e => e.publish())
    return (
      await Promise.all(e),
      l.debug("sharedState.dirty = false"),
      (this.sharedState.dirty = !1),
      (this.sharedState.lastPublished = new Date()),
      this.sharedState.commit(),
      this.sharedStore && this.sharedStore.update(this.sharedState),
      this.afterPublishCallback && (await this.afterPublishCallback()),
      this.sharedState.lastPublished
    )
  }
  static async resetEditStore(e) {
    const t = this.configuredSyncs.find(t => t.config.editStoreKey === e)
    t && (await t.reset())
  }
  static getStateOfEditStores() {
    const e = []
    return (
      this.configuredSyncs.forEach(t => {
        void 0 !== t.config.editStoreKey && e.push({ key: t.config.editStoreKey, dirty: t.isDirty() })
      }),
      e
    )
  }
  static get globalSession() {
    return this.sharedState
  }
  isDirty() {
    return this.state === d.DIRTY
  }
  async load() {
    if (this.state !== d.IDLE) throw Error("Data already loaded")
    this.setState(d.LOADING)
    const e = this.config.publishStores && this.config.publishStores.length > 0 ? this.config.publishStores.map(e => this.loadStore(e)) : []
    this.config.editStore && e.push(this.loadStore(this.config.editStore))
    this.config.legacyEditStore && e.push(this.loadStore(this.config.legacyEditStore, !0))
    const t: any[] = await Promise.all(e),
      i = (e, t, i) => (t && t.modified && i.modified && t.modified > i.modified ? t : (e && this.setState(d.DIRTY), i)),
      s = (e, t, i, n?, s?) => {
        if (!t) return e && this.setState(d.DIRTY), i
        if (n && s && s < n) return t
        for (const n in i) i.hasOwnProperty(n) && (e && this.setState(d.DIRTY), (t[n] = i[n]))
        return t
      }
    if (((this.data = t[0].data || {}), this.setState(d.CLEAN), (this.publishedData = funcUtil.deepCopy(this.data)), this.collectionData))
      for (let e = 1; e < t.length; e++) {
        const n = e >= this.config.publishStores.length,
          r: any = t[e]
        if (r.data) {
          for (const e in r.data)
            r.data.hasOwnProperty(e) &&
              (this.data[e]
                ? this.config.partialData
                  ? (this.data[e] = s(n, this.data[e], r.data[e]))
                  : (this.data[e] = i(n, this.data[e], r.data[e]))
                : (n && this.setState(d.DIRTY), (this.data[e] = r.data[e])))
          if (r.isLegacy) for (const e in this.data) r.data[e] || (n && this.setState(d.DIRTY), (this.data[e] = null), (this.diff[e] = null))
          r.isLegacy || (this.diff = r.data)
        }
      }
    else
      for (let e = 1; e < t.length; e++) {
        const n = e >= this.config.publishStores.length,
          r = t[e].data
        if (!r) continue
        const o = r
        if (this.config.partialData) {
          const e = this.data.modified instanceof Date ? this.data.modified : void 0,
            t = o.modified instanceof Date ? o.modified : void 0
          this.data = s(n, this.data, o, e, t)
        } else this.data = i(n, this.data, o)
      }
    for (const e in this.data) this.data[e] || (this.deletedKeys.push(e), delete this.data[e])
    return this.data
  }
  loadStore(e, t = !1) {
    return e.read().then(e => ({ data: e, isLegacy: t }))
  }
  get monitorNeeded() {
    return void 0 !== this.config.editStore || void 0 !== this.config.legacyEditStore || this.config.publishStores.length > 0
  }
  addMonitor(e: MarketMonitor | MapMonitor | Monitor) {
    if (!this.monitorNeeded) throw Error("Adding a monitor unnecessarily!")
    this._monitor = e
    this._monitor.onChanged(async () => {
      if ((this.onDataChanged(), this.config.safeKeys)) {
        let e = !1
        if (this.collectionData) for (const t in this.diff) for (const i in this.diff[t]) -1 !== this.config.safeKeys.indexOf(i) && (e = !0)
        else for (const t in this.diff) -1 !== this.config.safeKeys.indexOf(t) && (e = !0)
        if (!e) return
      }
      this.setState(d.DIRTY), await this.saveSession()
    })
  }
  onDataChanged() {}
  get monitor() {
    return this._monitor
  }
  setState(e) {
    this.config.extras & c.LEGACY_SYNC
      ? e !== d.DIRTY ||
        DataSync.sharedState.dirtyWS2 ||
        (l.debug("sharedState.dirtyWS2 = true"), (DataSync.sharedState.dirtyWS2 = !0), DataSync.sharedState.commit())
      : ((this.state = e),
        this.state !== d.DIRTY ||
          DataSync.sharedState.dirty ||
          (l.debug("sharedState.dirty = true"), (DataSync.sharedState.dirty = !0), DataSync.sharedState.commit()))
  }
  async saveSession() {
    l.info("Saving session..."), DataSync.sharedState.sessionsSaving++, DataSync.sharedState.commit()
    try {
      this.config.editStore && (await this.config.editStore.update(this.data, this.diff)),
        !this.config.legacyEditStore || this.config.extras & c.IGNORE_LEGACY_SAVE || (await this.config.legacyEditStore.update(this.data, this.diff))
    } catch (e) {
      l.error(e), DataSync.msgBus.broadcast(new SaveErrorMessage(e))
    }
    DataSync.sharedState.sessionsSaving--, DataSync.sharedState.commit()
  }
  async publish() {
    if (this.state === d.DIRTY) {
      for (const e of this.config.publishStores) {
        if (!this.isWritable(e)) throw Error("Trying to publish with readonly store")
        try {
          await e.update(this.data, this.diff), (this.publishedData = funcUtil.deepCopy(this.data))
        } catch (t) {
          throw (l.error("Failed to publish data: ", e.config, this.data, this.diff), t)
        }
      }
      try {
        this.config.editStore && (await this.config.editStore.delete()),
          !this.config.legacyEditStore || this.config.extras & c.IGNORE_LEGACY_SAVE || (await this.config.legacyEditStore.delete()),
          this.setState(d.CLEAN)
      } catch (e) {
        throw Error("Failed to clear session data after publishing: " + e)
      }
    }
  }
  async reset() {
    switch (this.state) {
      case d.CLEAN:
        return this.data
      case d.IDLE:
        throw Error("Attempting to reset a Sync that was not loaded.")
      case d.LOADING:
        throw Error("Attempting to reset a Sync that is still loading.")
      case d.DIRTY:
        l.info("Clearing session..."), DataSync.sharedState.sessionsSaving++, DataSync.sharedState.commit()
        try {
          this.config.editStore && (await this.config.editStore.delete()),
            !this.config.legacyEditStore || this.config.extras & c.IGNORE_LEGACY_SAVE || (await this.config.legacyEditStore.delete()),
            (this.data = funcUtil.deepCopy(this.publishedData)),
            this.setState(d.CLEAN)
        } catch (e) {
          l.error(e), DataSync.msgBus.broadcast(new SaveErrorMessage(e))
        }
        return DataSync.sharedState.sessionsSaving--, DataSync.sharedState.commit(), this.data
    }
  }
  isWritable(e) {
    return void 0 !== e.update
  }
}
DataSync.sharedState = new GlobalState()
DataSync.configuredSyncs = []
export class ObjectSync extends DataSync {
  constructor(e) {
    super(e, !1)
  }
  onDataChanged() {
    const e = this.monitor.getDiffRecord()
    this.diff = e
    this.diff.modified = new Date()
  }
}
export class CollectionSync extends DataSync {
  constructor(e) {
    super(e, !0)
  }
  onDataChanged() {
    const e = this.monitor.getDiffRecord()
    for (const t of e)
      switch (t.action) {
        case ChangeAction.added:
          this.diff[t.index] = t.diff
          break
        case ChangeAction.updated:
          this.diff[t.index] || (this.diff[t.index] = {}), funcUtil.merge(this.diff[t.index], t.diff)
          break
        case ChangeAction.removed:
          delete this.diff[t.index], (this.data[t.index] = null)
      }
    this.monitor.clearDiffRecord()
  }
}
